﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenMI.Standard;
using Oatc.OpenMI.Sdk.Spatial;
using Oatc.OpenMI.Sdk.Wrapper;
using Oatc.OpenMI.Sdk.Buffer;
using Oatc.OpenMI.Sdk.Backbone;
using Oatc.OpenMI.Sdk.DevelopmentSupport;
using HydroDesktop.Interfaces.ObjectModel;
using HydroDesktop.Database;
using System.IO;
using SharpMap.Geometries;
using System.Data.SqlClient;
using HydroDesktop.Interfaces;
using HydroDesktop.Configuration;

namespace CUAHSI.HIS
{
    public class DbWriter : ILinkableComponent, IListener
    {
        
        private Dictionary<string, ILink> _links
               = new Dictionary<string, ILink>();
        private List<IInputExchangeItem> _inputExchangeItems
            = new List<IInputExchangeItem>();
        private List<IOutputExchangeItem> _outputExchangeItems
            = new List<IOutputExchangeItem>();
        private double _earliestInputTime;
        private double _latestInputTime;
        private string _dbPath;
        private SmartBuffer _smartBuffer = new SmartBuffer();
        //private string _fullPath; //path to the .dll
        Dictionary<string, Series> serieses = new Dictionary<string, Series>();
        Dictionary<string, Theme> themes = new Dictionary<string, Theme>();
        Dictionary<string, Series> dataSeries = new Dictionary<string, Series>();
        string conn = null;
        double _ignore = -999;
        int new_series_count = 0;
        //private DbOperations _db;

        #region ILinkableComponent Members

        /// <summary>
        /// This method is called when links are created by the component
        /// </summary>
        /// <param name="link">OpenMI link object</param>
        public void AddLink(ILink link)
        {
            //subscribe to events
            ILinkableComponent LC = link.SourceComponent;
            for (int i = 0; i < GetAcceptedEventTypeCount(); i++)
            {
                EventType ev = GetAcceptedEventType(i);
                LC.Subscribe(this, ev);
            }

            //build HD data model object to store time-series
            CreateSeries(link);

            //store the link for future reference
            _links.Add(link.ID, link);
        }

        /// <summary>
        /// Returns the description of the component
        /// </summary>
        public string ComponentDescription
        {
            get { return "DbWriter 1.0"; }
        }

        /// <summary>
        /// Returns the components id
        /// </summary>
        public string ComponentID
        {
            get { return "DbWriter"; }
        }

        /// <summary>
        /// not implemented
        /// </summary>
        public void Dispose()
        {

        }

        /// <summary>
        /// Returns the earliest need time to execute calculation.  This is used to remove "old" values from memory during simulation
        /// </summary>
        public ITimeStamp EarliestInputTime
        {
            get { return new TimeStamp(_earliestInputTime); }
        }

        /// <summary>
        /// Writes HD time series data object to the data repository
        /// </summary>
        public void Finish()
        {
            RepositoryManagerSQL db = null; 
            
            //check to see if the database path is overridden
            if (conn != null)
                db = new RepositoryManagerSQL(DatabaseTypes.SQLite, conn);
            else
                db = new RepositoryManagerSQL(DatabaseTypes.SQLite, Settings.Instance.DataRepositoryConnectionString);

            //write each series to the database
            foreach (Series series in serieses.Values)
            {
                //get the linkID stored in CreateSeries()
                string linkID = series.Source.ISOMetadata.MetadataLink;

                //get the theme corresponding to the link
                Theme thisTheme = themes[linkID];

                //save data
                db.SaveSeriesAsCopy(series, thisTheme);   
            }
        
            //clear all values in the buffer
            _smartBuffer.Clear(this.TimeHorizon);
        }

        /// <summary>
        /// Used to retrieve input exchange items by index
        /// </summary>
        /// <param name="inputExchangeItemIndex">index</param>
        /// <returns>input exchange item</returns>
        public IInputExchangeItem GetInputExchangeItem(int inputExchangeItemIndex)
        {
            return _inputExchangeItems[inputExchangeItemIndex];
        }

        /// <summary>
        /// Used to retrieve output exchange items by index
        /// </summary>
        /// <param name="outputExchangeItemIndex">index</param>
        /// <returns>output exchange item</returns>
        public IOutputExchangeItem GetOutputExchangeItem(int outputExchangeItemIndex)
        {
            return _outputExchangeItems[outputExchangeItemIndex];
        }

        /// <summary>
        /// Not implemented. Returns an empty data because this component should not be used to supply data to other models
        /// </summary>
        /// <param name="time">requested time</param>
        /// <param name="linkID">requested link</param>
        /// <returns>empty scalarset</returns>
        public IValueSet GetValues(ITime time, string linkID)
        {
            return new ScalarSet();
        }

        /// <summary>
        /// This method is used to construct the component
        /// </summary>
        /// <param name="properties">arguments stored in the *.omi file</param>
        public void Initialize(IArgument[] properties)
        {
            //set connection string equal to the repository that HD is connected to
            conn = Settings.Instance.DataRepositoryConnectionString;

            //extract argument(s) from OMI file
            foreach (IArgument property in properties)
            {
                //overwrite the connection string, if one is given in the *.omi
                if (property.Key == "DbPath") 
                { 
                    _dbPath = property.Value;
                    FileInfo fi = new FileInfo(_dbPath);
                    conn = @"Data Source = " + fi.FullName + ";New=False;Compress=True;Version=3";
                }
                //default value for relationFactor is 1;
                if (property.Key == "Relaxation") { _smartBuffer.RelaxationFactor = Convert.ToDouble(property.Value); }
                if (property.Key == "IgnoreValue") { _ignore = Convert.ToDouble(property.Value); }
            }
            
            //create generic input and output exchange items
            InputExchangeItem inExchangeItem = new InputExchangeItem();
            inExchangeItem.ElementSet = new ElementSet("any element set", "any element set", ElementType.IDBased, null);
            inExchangeItem.Quantity = new Quantity("any quantity");
            _inputExchangeItems.Add(inExchangeItem);
            OutputExchangeItem outExchangeItem = new OutputExchangeItem();
            outExchangeItem.ElementSet = new ElementSet("dummy element set", "dummy element set", ElementType.IDBased, null);
            outExchangeItem.Quantity = new Quantity("dummy quantity");
            _outputExchangeItems.Add(outExchangeItem);

            //define arbitrary start and end times
            _earliestInputTime = CalendarConverter.Gregorian2ModifiedJulian(new DateTime(1900,1,1));
            _latestInputTime = CalendarConverter.Gregorian2ModifiedJulian(new DateTime(2100, 12, 31));

        }

        /// <summary>
        /// The number of input exchange items
        /// </summary>
        public int InputExchangeItemCount
        {
            get { return _inputExchangeItems.Count; }
        }

        /// <summary>
        /// description of the model component
        /// </summary>
        public string ModelDescription
        {
            get { return "DbWriter"; }
        }

        /// <summary>
        /// The id of the model.  This is the name shown up in the configuration window
        /// </summary>
        public string ModelID
        {
            get { return "DbWriter"; }
        }

        /// <summary>
        /// number of output exchange items
        /// </summary>
        public int OutputExchangeItemCount
        {
            get { return _outputExchangeItems.Count; }
        }

        /// <summary>
        /// not implemented
        /// </summary>
        public void Prepare()
        {
            
        }

        /// <summary>
        /// This is called when a link is removed from the compostion.
        /// </summary>
        /// <param name="linkID">id of the link that was removed</param>
        public void RemoveLink(string linkID)
        {
            _links.Remove(linkID);
        }

        /// <summary>
        /// Defines the model time horizon
        /// </summary>
        public ITimeSpan TimeHorizon
        {
            get { return new Oatc.OpenMI.Sdk.Backbone.TimeSpan(new TimeStamp(_earliestInputTime), new TimeStamp(_latestInputTime)); }
        }

        /// <summary>
        /// Not implemented
        /// </summary>
        /// <returns>validation token</returns>
        public string Validate()
        {
            return "Validate is not implemented";
        }

        #endregion

        #region IPublisher Members

        /// <summary>
        /// returns all of the events that are recognized by this component
        /// </summary>
        /// <param name="providedEventTypeIndex">index</param>
        /// <returns>an event</returns>
        public EventType GetPublishedEventType(int providedEventTypeIndex)
        {
            switch (providedEventTypeIndex)
            {
                case 0:
                    return EventType.DataChanged;
                case 1:
                    return EventType.TargetBeforeGetValuesCall;
                case 2:
                    return EventType.SourceAfterGetValuesCall;
                case 3:
                    return EventType.TargetBeforeGetValuesCall;
                case 4:
                    return EventType.TargetAfterGetValuesReturn;
                case 5:
                    return EventType.Informative;
                default:
                    throw new Exception("Iligal index in GetPublishedEventType()");
            }
        }

        /// <summary>
        /// Defines the number of recognized events
        /// </summary>
        /// <returns>6</returns>
        public int GetPublishedEventTypeCount()
        {
            return 6;
        }

        /// <summary>
        /// Not implemented
        /// </summary>
        /// <param name="Event">the event that is sent</param>
        public void SendEvent(IEvent Event)
        {

        }

        public void Subscribe(IListener listener, EventType eventType)
        {

        }

        public void UnSubscribe(IListener listener, EventType eventType)
        {

        }

        #endregion

        #region IListener Members

        /// <summary>
        /// defines events that are accepted by this component
        /// </summary>
        /// <param name="acceptedEventTypeIndex">index</param>
        /// <returns>an event</returns>
        public EventType GetAcceptedEventType(int acceptedEventTypeIndex)
        {
            switch (acceptedEventTypeIndex)
            {
                case 0:
                    return EventType.DataChanged;
                case 1:
                    return EventType.TargetBeforeGetValuesCall;
                case 2:
                    return EventType.SourceAfterGetValuesCall;
                case 3:
                    return EventType.TargetBeforeGetValuesCall;
                case 4:
                    return EventType.TargetAfterGetValuesReturn;
                case 5:
                    return EventType.Informative;
                default:
                    throw new Exception("Iligal index in GetPublishedEventType()");
            }       
        }

        /// <summary>
        /// the number of accepted events
        /// </summary>
        /// <returns>6</returns>
        public int GetAcceptedEventTypeCount()
        {
            return 6;
        }

        /// <summary>
        /// build HD data model to store time-series data
        /// </summary>
        /// <param name="link">the link that was added to the composition</param>
        public void CreateSeries(ILink link)
        {

            #region Create DataModel Objects [HACK]
            
            //create variable
            HydroDesktop.Interfaces.ObjectModel.Unit VarUnit = new HydroDesktop.Interfaces.ObjectModel.Unit();
            VarUnit.Name = link.SourceQuantity.Unit.Description;
            VarUnit.Abbreviation = link.SourceQuantity.Unit.ID;
            VarUnit.UnitsType = link.SourceQuantity.ValueType.ToString();

            //create time unit
            //TODO: Get these from the trigger
            HydroDesktop.Interfaces.ObjectModel.Unit TimeUnit = new HydroDesktop.Interfaces.ObjectModel.Unit();
            TimeUnit.Name = "Time";
            TimeUnit.Abbreviation = "none";
            TimeUnit.UnitsType = "Irregular";
            
            //create unit
            HydroDesktop.Interfaces.ObjectModel.Unit unit = new HydroDesktop.Interfaces.ObjectModel.Unit();
            unit.Abbreviation = link.SourceQuantity.Unit.ID;

            //create method
            HydroDesktop.Interfaces.ObjectModel.Method method = new Method();
            method.Link = link.SourceComponent.ModelID;
            if (link.SourceComponent.ComponentID == null)
                method.Link = "unknown";
            method.Description = link.SourceComponent.ModelDescription;
            if (link.SourceComponent.ComponentDescription == null)
                method.Description = "unknown";

            //define data service info
            DataServiceInfo dataservice = new DataServiceInfo();
            dataservice.Abstract = "none";
            dataservice.Citation = "none";
            dataservice.ContactEmail = "none";
            dataservice.ContactName = "none";
            dataservice.EastLongitude = -999;
            dataservice.HarveDateTime = DateTime.Now;
            dataservice.Id = -999;
            dataservice.NorthLatitude = -999;
            dataservice.HISCentralID = -999;
            dataservice.ServiceCode = "none";
            dataservice.DescriptionURL = "none";
            dataservice.EndpointURL = "none";
            dataservice.ServiceName = "none";
            dataservice.Protocol = "none";
            dataservice.ServiceTitle = "none";
            dataservice.ServiceType = "none";
            dataservice.Version = -999;
            dataservice.SiteCount = link.SourceElementSet.ElementCount;

            //create metadata
            ISOMetadata meta = new ISOMetadata();
            meta.Abstract = "none";
            //meta.DataService = dataservice;
            meta.Id = -999;
            meta.ProfileVersion = "none";
            meta.Title = "none";
            meta.TopicCategory = "none";
            //this field is used to link the dataseries to the themes in the Finish method
            meta.MetadataLink = link.ID ;

            //create source
            Source source = new Source();
            source.Organization = "University of South Carolina";
            source.Address = "none";
            source.Citation = "none";
            source.City = "none";
            source.ContactName = "none";
            //source.DataService = dataservice;
            source.Description = "none";
            source.Email = "none";
            source.Id = -999;
            source.Link = "none";
            source.OriginId = -999;
            source.Phone = "none";
            source.State = "none";
            source.ZipCode = -999;
            source.ISOMetadata = meta;

            //create variable
            Variable variable = new Variable();
            variable.Code = link.SourceQuantity.Description;
            variable.Name = link.SourceQuantity.ID;
            variable.VariableUnit = VarUnit;
            variable.TimeUnit = TimeUnit;
            variable.Speciation = "unknown";
            variable.GeneralCategory = "none";
            variable.NoDataValue = -999;
            variable.SampleMedium = "unknown";
            variable.TimeSupport = -999;
            variable.VocabularyPrefix = "none";
            variable.ValueType = "scalar";
            variable.DataType = "unknown";

            //variable.VariableUnit.Abbreviation = link.SourceQuantity.Unit.Description;



            //create qualControl
            QualityControlLevel qualControl = new QualityControlLevel();
            qualControl.Code = "qual1";
            qualControl.Definition = "Quality control level 1";
            qualControl.Id = 1;
            qualControl.Explanation = "unknown";
            qualControl.OriginId = -999;
            //qualControl.DataService = dataservice;

            #endregion


            #region Build Sites
            
            RepositoryManagerSQL db = null;

            //check to see if the database path is overridden
            if (conn != null)
            {
                db = new RepositoryManagerSQL(DatabaseTypes.SQLite, conn);
            }
            else
            {
                db = new RepositoryManagerSQL(DatabaseTypes.SQLite, Settings.Instance.DataRepositoryConnectionString);
                conn = Settings.Instance.DataRepositoryConnectionString;
            }
                
            

            Dictionary<long, Site> sites = new Dictionary<long, Site>();

            //loop through all elements in the source components element set
            for (int i = 0; i < link.SourceElementSet.ElementCount; ++i)
            {
                //define spatial reference
                HydroDesktop.Interfaces.ObjectModel.SpatialReference spatial = new HydroDesktop.Interfaces.ObjectModel.SpatialReference();
                spatial.Id = -999;
                spatial.Notes = "none";
                spatial.SRSID = -999;
                spatial.SRSName = "none";


                //--- create site ---
                Site site = new Site();

                //create a unique site name [variable_model_location]
                site.Name = (link.SourceElementSet.ID + "_" + link.SourceComponent.ModelID + "_loc" + i.ToString()).Replace(' ', '_');

                //check if a sitename already exists in the repository
                string sql = "SELECT s.SiteCode, s.SiteID " +
                                                    "FROM Sites s " +
                                                    "WHERE s.SiteName= '" + site.Name + "' ";
                
                DbOperations _db = new DbOperations(conn, DatabaseTypes.SQLite);
                System.Data.DataTable tbl = _db.LoadTable("values", sql);

                if (tbl.Rows.Count > 0)
                {
                    site.Code = tbl.Rows[0].ItemArray[0].ToString();
                    site.Id = Convert.ToInt32(tbl.Rows[0].ItemArray[1]);
                }
                else
                {
                    //create a new site
                    sql = "SELECT s.SiteCode, s.SiteID FROM Sites s ";

                    _db = new DbOperations(conn, DatabaseTypes.SQLite);
                    tbl = _db.LoadTable("values", sql);
                    int last_row = tbl.Rows.Count - 1;

                    //store the new site code and id info
                    site.Code = (Convert.ToInt32(tbl.Rows[last_row].ItemArray[0]) + 1 + new_series_count).ToString();
                    site.Id = Convert.ToInt32(tbl.Rows[last_row].ItemArray[0]) + 2 + new_series_count;

                    //add 1 to new series count so that the same site code isn't selected twice
                    new_series_count++;
                }

                site.SpatialReference = spatial;
                site.Comments = "none";
                site.County = "none";                
                site.Elevation_m = -999;
                site.Latitude = -999;                
                site.LocalProjection = spatial;                
                site.LocalX = -999;
                site.LocalY = -999;
                site.Longitude = -999;
                //site.DataService = dataservice;
                //site.LatLongDatumID = -999;
                //site.LocalProjectionID = -999;
                

                //--- Attempt to spatially define elements 7-15-2010 ---
                try
                {
                    //save site latitude and longitude
                    if (link.SourceElementSet.ElementType == ElementType.XYPoint)
                    {
                        site.Latitude = link.SourceElementSet.GetYCoordinate(i, 0);
                        site.Longitude = link.SourceElementSet.GetXCoordinate(i, 0);
                        site.LocalX = 0;
                        site.LocalY = 0;
                    }
                    else
                    {
                        ////List<Point> points = new List<Point>();
                        ////for(int p=0; p<= link.SourceElementSet.GetVertexCount(i)-1; p++)
                        ////{
                        ////        Point point = new Point(link.SourceElementSet.GetXCoordinate(i, p),
                        ////            link.SourceElementSet.GetYCoordinate(i, p));

                        ////        points.Add(point);
                        ////}
                        //////create polyline
                        ////LineString ls = new LineString(points);

                        ////new SharpMap.CoordinateSystems.ProjectedCoordinateSystem()
                        ////ls.SpatialReference = 

                        site.Latitude = link.SourceElementSet.GetYCoordinate(i, 0);
                        site.Longitude = link.SourceElementSet.GetXCoordinate(i, 0);

                        //link.SourceElementSet.SpatialReference.ID;
                        site.LocalX = 0;
                        site.LocalY = 0;



                    }
                }
                catch (Exception e) { }


                site.NetworkPrefix = "none";
                site.PosAccuracy_m = -999;
                site.State = "none";
                //site.TimeZone = new TimeZoneInfo.TransitionTime();
                
                site.VerticalDatum = "unknown";

                if (!sites.ContainsKey(site.Id))
                {
                    //add site and series to dictionary if they don't already exist
                    sites.Add(site.Id, site);
                    Series series = new Series(site, variable, method, qualControl, source);
                    series.Id = i;
                    if (!serieses.ContainsKey(site.Name)) { serieses.Add(site.Name, series); }
                    //else{serieses.Add(site.Name, series);}

                }
            }
            #endregion

        }

        /// <summary>
        /// Defines the actions that occur every time an event is recognized
        /// </summary>
        /// <param name="anEvent">the event that was triggered</param>
        public void OnEvent(IEvent anEvent)
        {

            if (anEvent.Type == EventType.DataChanged)
            {
                //get the current time
                TimeStamp ts = (TimeStamp)anEvent.SimulationTime;

                //get values
                ScalarSet vals = new ScalarSet();
                Link link = new Link();
                string linkID = null;
                foreach (string key in this._links.Keys)
                {
                    link = (Link)_links[key];

                    //make sure it gets values from the link that sent the event
                    if(link.TargetComponent == this && link.SourceComponent.ModelID == anEvent.Sender.ModelID)
                    {
                        //define theme
                        string themeDescription = link.SourceElementSet.Description;
                        string themeName = link.SourceElementSet.ID;
                        Theme theme = new Theme(themeName, themeDescription);

                        //get link values
                        vals = (ScalarSet)anEvent.Sender.GetValues(ts, key);

                        //save link theme
                        if (!themes.ContainsKey(link.ID))
                            themes.Add(link.ID, theme);

                        //save link id
                        linkID = link.SourceElementSet.ID;

                        break;
                    }
                }

                //if values are found, then store them 
                if (vals.data.Length > 0)
                {
                    int j = 0;
                    try
                    {
                        while (j <= serieses.Count - 1)
                        {
                            //for(int k=0;k<= vals.data.Length-1;k++)
                            //{

                                //checks to see if the value should be ignored.  This is defined in omi.
                                if (vals.data[j] != _ignore) 
                                {
                                    //get the site name
                                    string id = link.SourceElementSet.GetElementID(j);
                                    if (id == "")
                                        id = link.SourceElementSet.ID+j.ToString();

                                    string sql = "SELECT s.SiteName " +
                                                    "FROM Sites s " +
                                                    "INNER JOIN DataSeries ds ON s.SiteID = ds.SiteID " +
                                                    "WHERE ds.SeriesID= '" + id + "' ";

                                    //"ORDER BY dv.DataValue ASC";
                                    DbOperations _db  = new DbOperations(conn, DatabaseTypes.SQLite);
                                    System.Data.DataTable tbl = _db.LoadTable("values", sql);

                                    //string siteName = linkID + j.ToString();
                                    string siteName = null;
                                    if (tbl.Rows.Count > 0)
                                        siteName = tbl.Rows[0].ItemArray[0].ToString();
                                    else
                                        siteName = (link.SourceElementSet.ID +"_"+ link.SourceComponent.ModelID +"_loc"+ j.ToString()).Replace(' ', '_');


                                    //check to see if series exists
                                    if (serieses.ContainsKey(siteName))
                                    {
                                        //get the series
                                        Series series = serieses[siteName];

                                        series.AddDataValue(CalendarConverter.ModifiedJulian2Gregorian(ts.ModifiedJulianDay), vals.data[j]);
                                    }
                                } 
                            //}
                            j++;
                        }
                    }
                    catch (IndexOutOfRangeException) { }
                }
            }
        }
        #endregion
    }
}
